package com.nseeod.download;

import java.io.File;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.sql.SQLException;
import java.util.Observable;

import com.nseeod.constant.AppConstants;
import com.nseeod.db.DButil;
import com.nseeod.util.FileUtils;

// This class downloads a file from a URL.
public class Download extends Observable implements Runnable {

	// Max size of download buffer.
	private static final int MAX_BUFFER_SIZE = 1024;

	// These are the status names.
	public static final String STATUSES[] = { "Downloading", "Paused",
			"Complete", "Cancelled", "Error", "Ready", "Imported",
			"Import Error", "Skiped" };

	// These are the status codes.
	public static final int DOWNLOADING = 0;
	public static final int PAUSED = 1;
	public static final int COMPLETE = 2;
	public static final int CANCELLED = 3;
	public static final int ERROR = 4;
	public static final int READY = 5;
	public static final int IMPORTED = 6;
	public static final int IMPERROR = 7;
	public static final int SKIPED = 8;

	private URL url; // download URL
	private long size; // size of download in bytes
	private int downloaded; // number of bytes downloaded
	private int status; // current status of download
	private int prevStatus; // current previous of download

	private String downloadLocation = "";

	// Constructor for Download.
	public Download(URL url) {
		this.url = url;
		size = -1;
		downloaded = 0;
		status = READY;
		// downloadLocation = AppConstants.getDataLocation();
		// Begin the download.
		// start();
	}

	public Download(URL url, String downloadLocation) {
		this.url = url;
		this.downloadLocation = downloadLocation;
		size = -1;
		downloaded = 0;
		status = READY;

		// Begin the download.
		// start();
	}

	// Get this download's URL.
	public String getUrl() {
		return url.toString();
	}

	// Get this download's size.
	public long getSize() {
		return size;
	}

	// Get this download's progress.
	public float getProgress() {
		return ((float) downloaded / size) * 100;
	}

	// Get this download's status.
	public int getStatus() {
		return status;
	}

	// Get this download's status.
	public int getPrevStatus() {
		return prevStatus;
	}

	// Pause this download.
	public void pause() {
		stateChanged(PAUSED);
	}

	// Resume this download.
	public void resume() {
		stateChanged(DOWNLOADING);
		start();
	}

	// Cancel this download.
	public void cancel() {
		stateChanged(CANCELLED);
	}

	// Mark this download as having an error.
	private void error() {
		stateChanged(ERROR);
	}

	// Start or resume downloading.
	public void start() {
		Thread thread = new Thread(this);
		thread.start();
		stateChanged(DOWNLOADING);
	}

	// Get file name portion of URL.
	private String getTempFileName(URL url) {
		String fileName = url.getFile();
		return AppConstants.getTempFolderPath() + "/"
				+ fileName.substring(fileName.lastIndexOf('/') + 1);

	}

	// Get file name portion of URL.
	private String getSaveFileName(URL url) {
		String fileName = url.getFile();
		fileName = fileName.substring(fileName.lastIndexOf('/') + 1);
		if (!"".equals(downloadLocation)) {
			fileName = downloadLocation + "/" + fileName;

		} else {
			fileName = AppConstants.getDataFolderPath() + "/" + fileName;
		}
		return fileName;
	}

	// Download file.
	public void run() {
		RandomAccessFile tempFile = null;
		File saveFile = null;
		InputStream stream = null;

		try {
			// Open connection to URL.
			HttpURLConnection connection = (HttpURLConnection) url
					.openConnection();

			// Specify what portion of file to download.
			connection.setRequestProperty("Range", "bytes=" + downloaded + "-");

			// Connect to server.
			connection.connect();

			// Make sure response code is in the 200 range.
			if (connection.getResponseCode() / 100 != 2) {
				error();
			}

			// Check for valid content length.
			int contentLength = connection.getContentLength();
			if (contentLength < 1) {
				error();
			}

			/*
			 * Set the size for this download if it hasn't been already set.
			 */
			if (size == -1) {
				size = contentLength;
				stateChanged(status);
			}

			// Open file and seek to the end of it.
			if (status == DOWNLOADING) {
				saveFile = new File(getSaveFileName(url));
				tempFile = new RandomAccessFile(getTempFileName(url), "rw");
				tempFile.seek(downloaded);
				stream = connection.getInputStream();
			}
			if (!saveFile.exists() || saveFile.length() != size) {
				while (status == DOWNLOADING) {
					/*
					 * Size buffer according to how much of the file is left to
					 * download.
					 */
					byte buffer[];
					if (size - downloaded > MAX_BUFFER_SIZE) {
						buffer = new byte[MAX_BUFFER_SIZE];
					} else {
						buffer = new byte[(int) (size - downloaded)];
					}

					// Read from server into buffer.
					int read = stream.read(buffer);
					if (read == -1)
						break;

					// Write buffer to file.
					tempFile.write(buffer, 0, read);
					downloaded += read;
					stateChanged(status);
				}

				/*
				 * Change status to complete if this point was reached because
				 * downloading has finished.
				 */
				if (status == DOWNLOADING) {
					stateChanged(COMPLETE);
					tempFile.close();
					FileUtils.copyFile(new File(this.getTempFileName(url)),
							new File(this.getSaveFileName(url)));
					DButil dbUtil = new DButil();
					dbUtil.load("eoddata", getTempFileName(url));
					FileUtils.deleteFile(new File(this.getTempFileName(url)));
					stateChanged(IMPORTED);
				}
			} else {
				stateChanged(SKIPED);
			}
		} catch (SQLException e) {
			stateChanged(IMPERROR);
		} catch (Exception e) {
			error();
		} finally {
			// Close file.
			if (tempFile != null) {
				try {
					tempFile.close();
				} catch (Exception e) {
				}
			}

			// Close connection to server.
			if (stream != null) {
				try {
					stream.close();
				} catch (Exception e) {
				}
			}
		}
	}

	// Notify observers that this download's status has changed.
	private void stateChanged(int status) {
		this.prevStatus = this.status;
		this.status = status;
		setChanged();
		notifyObservers();
	}

	public String getDownloadLocation() {
		return downloadLocation;
	}

	public void setDownloadLocation(String thisDownloadLocation) {
		this.downloadLocation = thisDownloadLocation;
	}
}